// Copyright (C) 2013 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <libaddressinput/address_input_helper.h>

#include <libaddressinput/address_data.h>
#include <libaddressinput/callback.h>
#include <libaddressinput/null_storage.h>
#include <libaddressinput/preload_supplier.h>
#include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h>

#include <string>
#include <utility>

#include <gtest/gtest.h>

#include "mock_source.h"
#include "testdata_source.h"

namespace {

using i18n::addressinput::AddressData;
using i18n::addressinput::AddressInputHelper;
using i18n::addressinput::BuildCallback;
using i18n::addressinput::Callback;
using i18n::addressinput::MockSource;
using i18n::addressinput::NullStorage;
using i18n::addressinput::PreloadSupplier;
using i18n::addressinput::scoped_ptr;
using i18n::addressinput::TestdataSource;

class AddressInputHelperTest : public testing::Test {
 protected:
  AddressInputHelperTest()
      // Our PreloadSupplier loads all data for a country at a time.
      : supplier_(new TestdataSource(true), new NullStorage),
        address_input_helper_(&supplier_),
        loaded_(BuildCallback(this, &AddressInputHelperTest::Loaded)) {}

  // Helper method to test FillAddress that ensures the PreloadSupplier has the
  // necessary data preloaded.
  void FillAddress(AddressData* address) {
    const std::string& region_code = address->region_code;
    if (!region_code.empty()) {
      supplier_.LoadRules(region_code, *loaded_);
    }
    address_input_helper_.FillAddress(address);
  }

 private:
  // Used to preload data that we need.
  void Loaded(bool success, const std::string&, int) { ASSERT_TRUE(success); }

  PreloadSupplier supplier_;
  const AddressInputHelper address_input_helper_;
  const scoped_ptr<const PreloadSupplier::Callback> loaded_;
  DISALLOW_COPY_AND_ASSIGN(AddressInputHelperTest);
};

TEST_F(AddressInputHelperTest, AddressWithMissingPostalCode) {
  AddressData address;
  address.region_code = "CX";
  address.administrative_area = "WA";

  // There is only one postal code for Christmas Island
  AddressData expected = address;
  expected.postal_code = "6798";
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingAdmin) {
  AddressData address;
  address.region_code = "US";
  address.postal_code = "58098";
  // Other data should be left alone.
  address.address_line.push_back("10 High St");

  // North Dakota has post codes starting with 58.
  AddressData expected = address;
  expected.administrative_area = "ND";
  FillAddress(&address);
  EXPECT_EQ(expected, address);

  address.administrative_area = "CA";  // Override the admin area.
  // Now, since the admin area was already filled in, we don't fix it, even
  // though it was correct.
  expected.administrative_area = "CA";
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingLowerLevel) {
  AddressData address;
  address.region_code = "TW";
  address.postal_code = "53012";

  /* This matches 二水鄉 - Ershuei Township. */
  AddressData expected = address;
  /* This locality is in 彰化縣 - Changhua County. */
  expected.administrative_area = "\xE5\xBD\xB0\xE5\x8C\x96\xE7\xB8\xA3";
  expected.locality = "\xE4\xBA\x8C\xE6\xB0\xB4\xE9\x84\x89";
  FillAddress(&address);
  EXPECT_EQ(expected, address);

  // Override the admin area.
  address.administrative_area = "Already filled in";
  expected.administrative_area = "Already filled in";
  address.locality = "";
  // However, the locality will still be filled in.
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingLowerLevelLatin) {
  AddressData address;
  address.region_code = "TW";
  address.postal_code = "53012";
  address.language_code = "zh-Latn";

  /* This matches 二水鄉 - Ershuei Township. */
  AddressData expected = address;
  /* This locality is in 彰化縣 - Changhua County. */
  expected.locality = "Ershuei Township";
  expected.administrative_area = "Changhua County";
  FillAddress(&address);
  EXPECT_EQ(expected, address);

  // Override the admin area.
  address.administrative_area = "Already filled in";
  expected.administrative_area = "Already filled in";
  address.locality = "";
  // However, the locality will still be filled in.
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingDependentLocality) {
  AddressData address;
  address.region_code = "KR";
  // This matches Danwon-gu district.
  address.postal_code = "425-111";

  AddressData expected = address;
  /* The province is Gyeonggi - 경기도. */
  expected.administrative_area = "\xEA\xB2\xBD\xEA\xB8\xB0\xEB\x8F\x84";
  /* The city is Ansan-si - 안산시. */
  expected.locality = "\xEC\x95\x88\xEC\x82\xB0\xEC\x8B\x9C";
  /* The district is Danwon-gu - 단원구 */
  expected.dependent_locality = "\xEB\x8B\xA8\xEC\x9B\x90\xEA\xB5\xAC";

  FillAddress(&address);
  EXPECT_EQ(expected, address);

  AddressData address_ko_latn;
  address_ko_latn.region_code = "KR";
  address_ko_latn.postal_code = "425-111";
  address_ko_latn.language_code = "ko-latn";

  expected = address_ko_latn;
  /* The province is Gyeonggi - 경기도. */
  expected.administrative_area = "Gyeonggi";
  /* The city is Ansan-si - 안산시. */
  expected.locality = "Ansan-si";
  /* The district is Danwon-gu - 단원구 */
  expected.dependent_locality = "Danwon-gu";

  FillAddress(&address_ko_latn);
  EXPECT_EQ(expected, address_ko_latn);
}

TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingMultipleValues) {
  AddressData address;
  address.region_code = "KR";
  // This matches Wando-gun and Ganjin-gun, both in Jeonnam province.
  address.postal_code = "527-111";

  AddressData expected = address;
  /* The province, Jeonnam - 전라남도 - is known, but we have several locality
   * matches so none of them are populated. */
  expected.administrative_area =
      "\xEC\xA0\x84\xEB\x9D\xBC\xEB\x82\xA8\xEB\x8F\x84";
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithInvalidPostalCode) {
  AddressData address;
  address.postal_code = "970";
  address.region_code = "US";

  // We don't expect any changes, since the postal code couldn't be determined
  // as valid.
  AddressData expected = address;
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithNoPostalCodeValidation) {
  AddressData address;
  address.postal_code = "123";
  address.region_code = "GA";

  // We don't expect any changes, since the postal code couldn't be determined
  // as valid - we have no information about postal codes in Gabon (or even that
  // they are in use).
  AddressData expected = address;
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, AddressWithInvalidOrMissingRegionCode) {
  AddressData address;
  address.postal_code = "XXX";
  address.administrative_area = "YYY";

  // We don't expect any changes, since there was no region code.
  AddressData expected = address;
  FillAddress(&address);
  EXPECT_EQ(expected, address);

  address.region_code = "XXXX";
  expected.region_code = "XXXX";
  // Again, nothing should change.
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperTest, RegionWithUnusedAdminAreaNames) {
  AddressData address;
  address.region_code = "CH";
  address.postal_code = "1111";
  address.language_code = "de-CH";

  // Administrative area should not be filled because it's not used. Locality
  // should not be filled because there's no data for it.
  AddressData expected = address;
  FillAddress(&address);
  EXPECT_EQ(expected, address);
}

class AddressInputHelperMockDataTest : public testing::Test {
 protected:
  AddressInputHelperMockDataTest()
      : source_(new MockSource),
        // Our PreloadSupplier loads all data for a country at a time.
        supplier_(source_, new NullStorage),
        address_input_helper_(&supplier_),
        loaded_(BuildCallback(this, &AddressInputHelperMockDataTest::Loaded)) {}

  // Helper method to test FillAddress that ensures the PreloadSupplier has the
  // necessary data preloaded.
  void FillAddress(AddressData* address) {
    const std::string& region_code = address->region_code;
    if (!region_code.empty()) {
      supplier_.LoadRules(region_code, *loaded_);
    }
    address_input_helper_.FillAddress(address);
  }

  MockSource* const source_;

 private:
  // Our mock source we assume will always succeed.
  void Loaded(bool success, const std::string&, int) { ASSERT_TRUE(success); }

  PreloadSupplier supplier_;
  const AddressInputHelper address_input_helper_;
  const scoped_ptr<const PreloadSupplier::Callback> loaded_;
  DISALLOW_COPY_AND_ASSIGN(AddressInputHelperMockDataTest);
};

TEST_F(AddressInputHelperMockDataTest,
       PostalCodeSharedAcrossDifferentHierarchies) {
  // Note that this data is in the format of data that would be returned from
  // the aggregate server.
  source_->data_.insert(std::make_pair(
      // We use KR since we need a country where we format all fields down to
      // dependent locality, or the hierarchy won't be loaded.
      "data/KR",
      "{\"data/KR\": "
      // The top-level ZIP expression must be present for sub-key matches to be
      // evaluated.
      "{\"id\":\"data/KR\", \"sub_keys\":\"A~B\", \"zip\":\"\\\\d{5}\"}, "
      "\"data/KR/A\": "
      "{\"id\":\"data/KR/A\", \"sub_keys\":\"A1\"}, "
      "\"data/KR/A/A1\": "
      "{\"id\":\"data/KR/A/A1\", \"zip\":\"1\"}, "
      "\"data/KR/B\": "
      "{\"id\":\"data/KR/B\", \"sub_keys\":\"B1\"}, "
      "\"data/KR/B/B1\": "
      "{\"id\":\"data/KR/B/B1\", \"zip\":\"12\"}}"));

  AddressData address;
  address.region_code = "KR";
  address.postal_code = "12345";
  address.administrative_area = "";

  AddressData expected = address;
  FillAddress(&address);
  // Nothing should have changed, since the ZIP code matches both of the cities,
  // and they aren't even in the same state.
  EXPECT_EQ(expected, address);
}

TEST_F(AddressInputHelperMockDataTest,
       PostalCodeSharedAcrossDifferentHierarchiesSameState) {
  // Create data where one state matches the ZIP code, but the other doesn't:
  // within the state which does, multiple cities and sub-cities match. The only
  // thing we can be certain of is therefore the state.
  source_->data_.insert(std::make_pair(
      // We use KR since we need a country where we format all fields down to
      // dependent locality, or the hierarchy won't be loaded.
      "data/KR",
      "{\"data/KR\": "
      // The top-level ZIP expression must be present for sub-key matches to be
      // evaluated.
      "{\"id\":\"data/KR\", \"sub_keys\":\"A~B\", \"zip\":\"\\\\d{5}\"}, "
      "\"data/KR/A\": "
      "{\"id\":\"data/KR/A\", \"sub_keys\":\"A1~A2\"}, "
      "\"data/KR/A/A1\": "
      "{\"id\":\"data/KR/A/A1\", \"sub_keys\":\"A1a\", \"zip\":\"1\"}, "
      // This key matches the ZIP code.
      "\"data/KR/A/A1/A1a\": "
      "{\"id\":\"data/KR/A/A1/A1a\", \"zip\":\"12\"}, "
      "\"data/KR/A/A2\": "
      "{\"id\":\"data/KR/A/A2\", \"sub_keys\":\"A2a\", \"zip\":\"1\"}, "
      // This key, also in state A, but in city A2, matches the ZIP code.
      "\"data/KR/A/A2/A2a\": "
      "{\"id\":\"data/KR/A/A2/A2a\", \"zip\":\"123\"}, "
      // This key, in state B, does not match the ZIP code.
      "\"data/KR/B\": "
      "{\"id\":\"data/KR/B\", \"zip\":\"2\"}}"));

  AddressData address;
  address.region_code = "KR";
  address.postal_code = "12345";
  address.administrative_area = "";

  AddressData expected = address;
  expected.administrative_area = "A";
  FillAddress(&address);
  // The ZIP code matches multiple city districts and cities; but only one
  // state, so we fill this in.
  EXPECT_EQ(expected, address);
}

}  // namespace
